package lib.gt.map;

import java.util.List;

import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.os.Bundle;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;

public class LocationManager implements LocationListener {
	
	private static final String TAG = "LocationManager";
	
	private static LocationManager instance = null;
	private static boolean sHasInit = false;
	
	private MapView mMapView;
	private Context mContext;
	private android.location.LocationManager mLocationManager;
	private boolean mAnimateToPointWhenLocationChanged;
	private int mLocationDrawableRes;
	private GeoPoint mLocationPoint;
	private MyLocationOverlay mLocationOverlay;
	private boolean mFirstLocateMe;
	private boolean mEnableFirstLocateMe;
	
	protected LocationManager(MapView mapView, int myLocationDrawableRes) {
		mMapView = mapView;
		mLocationDrawableRes = myLocationDrawableRes;
		mEnableFirstLocateMe = true;
		mFirstLocateMe = true;
		sHasInit = true;
		if(mMapView != null) {
			mContext = mMapView.getContext();
			mLocationManager = (android.location.LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
			mLocationOverlay = new MyLocationOverlay(mContext.getResources().getDrawable(mLocationDrawableRes));
		}
	}
	
	public synchronized static void init(MapView mapView, int myLocationDrawableRes) {
		if(instance == null) {
			instance = new LocationManager(mapView, myLocationDrawableRes);
		}
	}
	
	public static LocationManager getInstance() {
		if(instance == null) {
			throw new NullPointerException("Please invoke init(mapView, myLocationDrawableRes) method before getInstance()!");
		}
		return instance;
	}
	
	public GeoPoint getCurrentLocationPoint() {
		return mLocationPoint;
	}
	
	public GeoPoint getLastKnownPoint() {
		GeoPoint lastKnownPoint = null;
		String provider = getProvider();
		if(provider != null) {
			Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
			if (lastKnownLocation != null) {
				lastKnownPoint = getLocationE6(lastKnownLocation);
			} else {
				MapLog.w(TAG, "lastKnowLocation is null");
			}
		}
		return lastKnownPoint;
	}
	
	public String getProvider() {
		Criteria criteria = new Criteria();
		criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
		criteria.setAccuracy(Criteria.NO_REQUIREMENT);
		String Provider = mLocationManager.getBestProvider(criteria, true);
		return Provider;
	}
	
	public GeoPoint getLocationE6(Location location) {
		GeoPoint currentPoint = new GeoPoint((int) (location.getLatitude() * 1E6), (int) (location.getLongitude() * 1E6));
		return currentPoint;
	}
	
	public void setAnimateToPointWhenLocationChanged(boolean enable) {
		mAnimateToPointWhenLocationChanged = enable;
	}
	
	public MyLocationOverlay getMyLocationOverlay() {
		return mLocationOverlay;
	}
	
	/**
	 * 提供一個實現了ILocation接口的List列表，從中找到離當前位置最近的一項.
	 * @param locations  一個實現了ILocation接口的List列表.
	 * @return  離當前位置最近的那一項，如當前位置獲取不到，則返回null.
	 */
	public ILocation findNearestLocationInCurrentLocation(List<? extends ILocation> locations) {
		ILocation theNearestLocation = null;
		if(locations != null && locations.size() > 0 && mLocationPoint != null) {
			final GeoPoint myLocationPoint = mLocationPoint;
			final int myLongitudeE6 = myLocationPoint.getLongitudeE6();
			final int myLatitudeE6 = myLocationPoint.getLatitudeE6();
			final float result[] = new float[3];
			final int size = locations.size();
			float distance = 0;
			int theNearestLocationIndex = 0;
			Location.distanceBetween(myLatitudeE6 / 1E6, myLongitudeE6 / 1E6, locations.get(0).getLatitudeE6() / 1E6, locations.get(0).getLongitudeE6() / 1E6, result);
			distance = result[0];
			for(int i = 0; i < size; i++) {
				ILocation point = locations.get(i);
				Location.distanceBetween(myLatitudeE6 / 1E6, myLongitudeE6 / 1E6, point.getLatitudeE6() / 1E6, point.getLongitudeE6() / 1E6, result);
				if(result[0] < distance) {
					distance = result[0];
					theNearestLocationIndex = i;
				}
			}
			if(theNearestLocationIndex >= 0 && theNearestLocationIndex < size) {
				theNearestLocation = locations.get(theNearestLocationIndex);
			}
		}
		return theNearestLocation;
	}
	
	public synchronized void updateMyLocation(GeoPoint point) {
		if(point != null && mMapView != null) {
			MapLog.i(TAG, "Updating my location.. " + point);
			mLocationPoint = point;
//			mMapView.getOverlays().clear();
			OverlayItem item = new OverlayItem(point, null, null);
			
			mMapView.getOverlays().remove(mLocationOverlay);
			mLocationOverlay.updateMyLocationOverlayItem(item);
			mMapView.getOverlays().add(mLocationOverlay);
//			mLocationOverlay.updateMyLocationOverlayItem(item);
//			mMapView.getOverlays().add(mLocationOverlay);
			mMapView.postInvalidate();
		}
	}
	
	public static boolean isGPSAvailable(Context context) {
		if(context == null) {
			return false;
		}
		boolean available = false;
		PackageManager pm = context.getPackageManager();
		if (pm.hasSystemFeature(PackageManager.FEATURE_LOCATION)) {
			android.location.LocationManager lm = (android.location.LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
			available = lm.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER) && lm.isProviderEnabled(android.location.LocationManager.NETWORK_PROVIDER);
		}
		return available;
	}

	@Override
	public void onLocationChanged(Location location) {
		if(location != null) {
			int lat = (int) (location.getLatitude() * 1E6);
			int lng = (int) (location.getLongitude() * 1E6);
			GeoPoint point = new GeoPoint(lat, lng);
			updateMyLocation(point);
			if(mFirstLocateMe && mMapView != null) {
				mFirstLocateMe = false;
				if(mEnableFirstLocateMe) {
					mMapView.getController().animateTo(point);
				}
			}
			else if(mAnimateToPointWhenLocationChanged && mMapView != null) {
				mMapView.getController().animateTo(point);
			}
		}
		else {
			MapLog.w(TAG, "Can not get current location.");
		}
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		MapLog.i(TAG, "onStatusChanged >> " + provider + " , status >> " + status);
	}

	@Override
	public void onProviderEnabled(String provider) {
		MapLog.i(TAG, "onProviderEnabled >> " + provider);
	}

	@Override
	public void onProviderDisabled(String provider) {
		MapLog.i(TAG, "onProviderDisabled >> " + provider);
	}
	
	/**
	 * Invoke this method in activity's onResume().
	 */
	public void startLocationListener() {
		if(mLocationManager != null) {
			String provider = getProvider();
			if(provider != null && !"".equals(provider.trim())) {
				mLocationManager.requestLocationUpdates(provider, 0, 0, this);
				updateMyLocation(getLastKnownPoint());
			}
		}
	}
	
	/**
	 * Invoke this method in activity's onPause().
	 */
	public void stopLocationListener() {
		if(mLocationManager != null) {
			mLocationManager.removeUpdates(this);
		}
	}
	
	public static boolean hasInit() {
		return sHasInit;
	}
	
	public void setEnableFirstLocateMe(boolean enable) {
		mEnableFirstLocateMe = enable;
	}
	
	/**<b>Note: <br/>
	 * Invoke this method in activity's onDestory(), or will cause memory leak.</b>
	 */
	public void destroy() {
		stopLocationListener();
		if(mMapView != null) {
			mMapView.getOverlays().remove(mLocationOverlay);
		}
		if(mLocationOverlay != null) {
			mLocationOverlay.release();
			mLocationOverlay = null;
		}
		mLocationPoint = null;
		mLocationManager = null;
		mMapView = null;
		mContext = null;
		instance = null;
		sHasInit = false;
	}
}
